// Ninf-G-XX, ver. 0.1
// Copyright 2011 National Institute of Advanced Industrial Science and Technology
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "remote_matrix.hh"

#include <iostream>
#include <fstream>
#include <stdexcept>
#include <cstdlib>
#include <string>
#include <cstring>
#include <cstdio>

#ifdef USE_CURL
#include <curl/curl.h>
CURL* curl_handle = NULL;
CURL* curl_handle1 = NULL;
#endif

// Endianness management
inline char system_endianness() {
  union { 
    int int_val;
    char c_val[sizeof(int)];
  } bg_endian = {0x01020304};
  
  if (bg_endian.c_val[0]==1)
    return BIGENDIAN;
  return LTLENDIAN;
}

void swap_endianness(void* data, const size_t& size) {
  char tmp;
  for (unsigned char i=0; i<size; ++i) {
    tmp = ((char*) data)[size - i - 1];
    ((char*) data)[size - i - 1] = ((char*) data)[i];
    ((char*) data)[i] = tmp;
  }
}

void load_matrix(const char* filename,
                 matrix_t& matrix)
{
  std::ifstream input(filename);
  
  if (!input.is_open())
    throw std::runtime_error("Unable to open file to read");
  
  input.read((char*) &matrix.desc, sizeof(mat_t));
  
  // alloc data
  matrix.data = (char*) calloc(matrix.desc.nb_col*matrix.desc.nb_row,
                               matrix.desc.elt_size);
  input.read(matrix.data,
             matrix.desc.elt_size*matrix.desc.nb_col*matrix.desc.nb_row);
  if (matrix.desc.endianness!=system_endianness()) {
    for (unsigned int i=0;
         i<matrix.desc.nb_col*matrix.desc.nb_row;
         i+=matrix.desc.elt_size)
      swap_endianness(matrix.data+i, matrix.desc.elt_size);
  }
}

void save_matrix(const char* filename,
                 const matrix_t& matrix)
{

  std::ofstream output(filename);

  if (!output.is_open())
    throw std::runtime_error("Unable to open file to write");

  output.write((char*) &matrix.desc, sizeof(struct mat_t));

  std::cout << matrix.desc.elt_size*matrix.desc.nb_col*matrix.desc.nb_row
	    << std::endl;
  
  
  output.write(matrix.data,
               matrix.desc.elt_size*matrix.desc.nb_col*matrix.desc.nb_row);

  output.close();

}

matrix_t* _newMatrix(const unsigned int& nb_col,
                     const unsigned int& nb_row,
                     const char& elt_size,
                     const char& order)
{
  matrix_t* result = new matrix_t;
  result->desc.nb_col = nb_col;
  result->desc.nb_row = nb_row;
  result->desc.elt_size = elt_size;
  result->desc.order = order;
  result->desc.endianness = system_endianness();
  result->offset = 0;
  result->data = (char*) calloc(nb_col*nb_row, elt_size);
  
  return result;
}



#ifdef USE_CURL
size_t write_data(void* buffer, size_t size, size_t nbmemb, matrix_t* matrix) {
  size_t buffer_offset = 0;
  
  if (matrix->offset==0) { // First part
    memcpy(&matrix->desc, buffer, sizeof(mat_t));
    matrix->data = (char*) malloc(matrix->desc.nb_col *
				  matrix->desc.nb_row *
				  matrix->desc.elt_size);
    buffer_offset=sizeof(mat_t);
  }
  
  memcpy(matrix->data+matrix->offset, (char*)buffer+buffer_offset, size*nbmemb-sizeof(mat_t));
  
  matrix->offset+=size*nbmemb;
  
  return size*nbmemb;
}

void get_matrix(const std::string& url, matrix_t& matrix) {
  if (!curl_handle)
    curl_handle=curl_easy_init();
  if (!curl_handle)
    throw std::runtime_error("Cannot initialize CURL handle");
  
  curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
  curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_data);
  curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &matrix);
  if (curl_easy_perform(curl_handle))
    throw std::runtime_error("Error downloading data");
}

void put_matrix(const std::string& url, const std::string& name,
                matrix_t& matrix) {
  if (!curl_handle1)
    curl_handle1=curl_easy_init();
  if (!curl_handle1)
    throw std::runtime_error("Cannot initialize CURL handle");
  
  // Use form to send a file.
  // First we save the matrix in temporary file
  char tmpfilename[L_tmpnam];
  tmpnam(tmpfilename);
  
  save_matrix(tmpfilename, matrix);
  
  struct curl_httppost * first, * last;
  first = NULL;
  last = NULL;

  curl_formadd(&first, &last, CURLFORM_COPYNAME, "upload",
               CURLFORM_FILE, tmpfilename, CURLFORM_END);
  curl_formadd(&first, &last, CURLFORM_COPYNAME, "name",
               CURLFORM_COPYCONTENTS, name.c_str(), CURLFORM_END);
  
  curl_easy_setopt(curl_handle1, CURLOPT_URL, url.c_str());
  curl_easy_setopt(curl_handle1, CURLOPT_HTTPPOST, first);
  if (curl_easy_perform(curl_handle1))
    throw std::runtime_error("Error uploading data");
  remove(tmpfilename);
}
#endif
